Chapter 3

Durk Jan de Bruin

The Calendar Shop

Calendar

Create your own calendar with illustrations from popular cartoonists. The Calendar Shop program shows how it can be done.

Keeping track of time is a major industry. Historically, calendars have defined weeks and months quite differently from today. This program will create a calendar for any given year. It can be altered to create fantasy calendars without Mondays and historical calendars with 10-day weeks or 28-day months.


Problem Statement

The Python Code section contains an incomplete program to print a calendar for a year specified by the user. An integer function, NumberOfDaysIn, and a function, PrintMonth, are missing. Add the code for the missing sub programs. Don't change any of the existing code. Sample output - the first half of the calendar for 2020 - appears on the next page. Verify that your program produces identical output for the given months.

The function NumberOfDaysIn determines the number of days in each month of the specified year. It requires two integer arguments representing a month (1 for January, 2 for February, and so on) and a year. It should return the specified number of days in the month. For January, March, May, July, August, October, and December, it should return 31. For April, June, September, and November, it should return 30. For February, it should return 28 or 29, depending on whether the year is a leap year.

Recall that a year is a leap year (its February has 29 days) when it is divisible by 4 and not by 100, or divisible by 400.

The function PrintMonth prints each month of the year as shown in the sample output. It takes three integer arguments: the month to be printed, the year (as in NumberOfDaysIn), and the first day of the month (0 for Sunday, 1 for Monday, and so on). Exactly one blank line is to be printed between successive months.

Analysis

3.1 Which of the following years are leap years: 2010, 2020, 2014, 2017, 2000?

Analysis, Reflection

3.2 What are aspects of the problem specification that potentially will lead to bugs in the code?


Preparation

This case study introduces repeat loops, functions, and boolean variables; it also uses if and case statements, functions, and value parameters.

Output for the First Half of 2020

January 2020

S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

February 2020

S M T W T F S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29

March 2020

S M T W T F S
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

April 2020

S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

May 2020

S M T W T F S
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31

June 2020

S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
  1. 1
  2. 2

Chapter 3

Durk Jan de Bruin

Understanding the Program

How should one start modifying the program?

Since this problem requires the completion of a program, a good first step is to try to understand what the program is supposed to do. In particular, it will help to understand the algorithm the program implements the sequence of steps it uses to solve the problem and the variables that it manipulates. We also need to understand the program's style in order to make modifications consistent with the existing code.

Stop & Predict

What are the first steps in figuring out how a program works?

What parts of the program tell about the algorithm and the variables?

To understand the algorithm implemented by a program, one starts by reading the main program, which ideally provides the major steps in the decomposition. To find out about the variables being manipulated, one inspects the constant and type definitions and variable declarations.

In a Python program, the main program appears at the end, since it must appear after all subprograms that it calls. In contrast, since variables must be declared before they are used in the main program or its subprograms, they come at the beginning of the program.

Stop & Help

How does one go about understanding individual functions in a Python program?

Should the algorithm or the variables be inspected first?

In this program, the algorithm is likely to be more complicated than the variables used, so reading the main program is a good place to start. Here is the main program:

ReadYearInfo(year, dayForFirst)
for month in range(JAN, DEC+1):
PrintMonth(month, year, dayForFirst)
dayForFirst = (dayForFirst + NumberOfDaysIn(month, year)) % 7

What is the algorithm for this program?

It appears that ReadYearInfo reads some information from the user. Since year and dayForFirst have no values before the program calls ReadYearInfo, we assume they receive values from that function as variable parameters.

Stop & Predict

Is it best to check out the details or continue with determining the algorithm?

Rather than checking ReadYearInfo to find out exactly what it does, we continue to examine the main program to get the "big picture" of the algorithm. Here we postpone looking at the details to avoid getting confused. In designing solutions, we postpone thinking about the details for the same reason.

What is the for statement doing?

Most for loops are indexed by integers, so the JAN and DEC come as a surprise. Examining the beginning of the program reveals that the integers 1, 2,...., 12 have been given names as Python constants. This use of one of Python's naming facilities provides valuable information; MAY is a much more meaningful term than 5 in this program.

It looks as if the loop is doing something for each month. This becomes clear when we examine the loop body. The program is printing the month and updating the variable dayForFirst.

What is the purpose of dayForFirst?

The comment accompanying dayForFirst says that it is the index of the first day of month. The comment accompanying the calling function, ReadYearInfo, gives more information: 0 stands for Sunday, 1 for Monday, and so on, and the value is computed by using a formula from a book. The value stored in dayForFirst thus tells how many "blank days" to print at the start of the first week of a month. (We note, as anticipated, that ReadYearInfo's parameters are both variable parameters.)

How is dayForFirst updated?

To update dayForFirst (that is, to find the first day of the next month), the program adds the number of days in the current month to dayForFirst, then eliminates all full weeks (that is, "mods" by 7).

We consider an example. Suppose that January starts on a Wednesday, as it did in 2020. Then dayForFirst for 2020 would be 3. We determine what day February of 2020 starts on by adding 31, the number of days in January, to 3 to get 34, and taking the remainder of 34 after division by 7, to get 6. Thus February starts on a Saturday.

Stop & Help

Determine the day on which March 2020 starts, given that February 2020 starts on Saturday.

What else does ReadYearInfo do?

Examining the rest of ReadYearInfo (starting at the end again), we find a loop that reads values from the user until one is "legal." Legality is defined by a boolean function, which in this case merely checks if the year is positive. Here is another instance of using a name to convey additional information. A test for year > 0 in ReadYearInfo could mean many things, but a test for the legality of a year clearly indicates that the code is checking that the program can handle the year.

The code to check for legal values is an instance of a variation of the "read one, process one" template introduced in Banners With CLASS. It can be called an "input and process until done" template and looks like this:

repeat
ask the user for input and read values for variables
if not Legal(variables)
print an error message
until Legal(variables)

  1. 3
  2. 4

Chapter 3

Durk Jan de Bruin

"Processing" here consists of checking for legality

What is the program decomposition?

By reading the program starting at the end, we have figured out what it does. The outline below provides a summary of what we know so far. Missing parts are boxed.

Stop & Predict

Write down the decomposition before looking at the outline.

Produce a calendar.
Read the year and find what day it starts on.
Read a legal year from the user.
Repeat the following until we have a legal year:
Ask the user for a year.
Read the response.
Check the response.
Find the day of the week of January 1 of that year.
Produce the calendar for the given year.
For each month, do the following:
Print the calendar entry for the month.
Determine the starting day of the next month.
Find the number of days in the month just printed.
Add it to the old starting day, and % by 7.

Stop & Help

Write the function headerfor NumberOfDaysIn and the function header for Print Month.

Should PrintMonth or NumberOfDaysIn be designed first?

In deciding which routine to design first, the main concern is that the design of one routine does not interfere with a good design for others. In addition, some people prefer to do hard things first, and others start with easy things.

PrintMonth at first glance looks more complicated than NumberOfDaysIn. Furthermore, PrintMonth will probably need to call NumberOfDaysIn to determine how many days to print for the month. Thus we start with NumberOfDaysIn.

Reflection

3.3 Compare the approach to reading code described so far to your own approach to reading code. What techniques mentioned in the commentary will you use in the future?

Reflection

3.4 Read a fellow programmer's code, and keep track of what is easy to understand and what is difficult.

Reflection

3.5 How would you have written the main program?What aspects of the code in the program framework were especially clear or especially confusing for you?

Analysis

3.6 Derive the "magic" formula for computing the day on which January 1 falls

Analysis

3.7 Explain why PrintMonth will never be given a non negative value for its year parameter.

Analysis

3.8 Why should dayForFirst be updated in the main program rather than in PrintMonth?

Reflection

3.9 Given a collection of tasks to perform, do you prefer to do the hard tasks first or the easy ones?Why?


Designing and Developing NumberOfDaysIn

What does NumberOfDaysIn do?

Given a month, NumberOfDaysIn is supposed to return the number of days it has. There are thirteen possibilities: two for February and one for each of the other months

Stop & Predict

What Python construct will be best for the body of NumberOfDaysIn?

This function could be implemented with a sequence of if...else tests. It returns essentially the same thing a number of days for each month.

if (month == JAN or month == MAR or month == MAY or month == JUL or month == AUG or month == OCT or month == DEC):
NumberOfDaysIn = 31
elif month == FEB:
the right thing for February
else:
NumberOfDaysIn = 30

How should the case for February be coded?

The decision for February could also be coded as an if statement. Better, however, would be code that's more similar to the other cases, namely an assignment statement. One way to do this is with a function that returns the number of days in February:

if month == FEB:
NumberOfDaysIn = FebDays(year)

An even better way to communicate the exceptional nature of the leap year is the following:

if month == FEB:
NumberOfDaysIn = 28 + LeapDay(year)

where LeapDay is an integer function returning 1 or 0 according to whether or not the year is a leap year.

  1. 5
  2. 6

Chapter 3

Durk Jan de Bruin

How is LeapDay coded?

Determining the value of LeapDay requires several tests. In general, people understand positive tests better than negative tests, so LeapDay appears as follows:

if (year % 4 == 0) and (year % 100 != 0):
LeapDay = 1
elif year % 400 == 0:
LeapDay = 1
else:
LeapDay = 0

The code for the complete NumberOfDaysIn appears in the Python Code section.

When and how should NumberOfDaysIn should be tested?

NumberOfDaysIn should be tested all by itself, so that any bugs within it NumberOfDaysIn be will be easy to locate. We can wait until the rest of the program is designed tested? to test NumberOfDaysIn. We can also test it now. What we gain by not waiting is a feeling of accomplishment once the code works and some encouragement to tackle the rest of the task.

To test NumberOfDaysIn all by itself, we create a simple main program that asks for a month and a year and prints what NumberOfDaysIn returns.

Stop & Help

Write a simple main program to test NumberOfDaysIn, identify good test values, and test the function.

What are good test cases?

As with any code, test values should be chosen at least to exercise all statements at least once. One cannot be really sure that a statement works correctly without executing it. Thus test values should be chosen that (a) exercise each part of the case statement in NumberOfDaysIn, and (b) exercise each part of the if statement in LeapDay. Thus the test values should include at least one of the months with 30 days, at least one of the months with 31 days, and several different years for February. (One might reasonably argue that test values should include every month.)

Stop & Predict

List all the leap year cases that need to be tested.

We make a table to help clarify the purpose of each test for February:

Test Year Leap Year? Reason
2019 no not divisible by 4
2016 yes divisible by 4 and not by 100
1900 no divisible by 4 and 100, but not by 400
2000 yes divisible by 400

Modification

3.10 Write and test the FebDays function mentioned in the commentary.

Debugging

3.11 Insert a bug into the NumberOfDaysIn function, let a fellow programmer test it without being able to read the code, and see how long he or she takes to find the bug. What kinds of bugs are easy for a tester to overlook.

Analysis

3.12 Suppose that LeapDay were rewritten as a four-part if...then...else as follows, with each if condition being a single equality test involving year and the % operator.

if _____:
LeapDay = ___
elif _____:
LeapDay = ___
elif _____:
LeapDay = ___
else:
LeapDay = ___

In which order(s) may the tests for year % 4 = 0, year % 100 = 0, and year % 400 = 0 be made?Explain.


Designing and Developing PrintMonth

The first step in designing PrintMonth is to break the problem into more manageable pieces by identifying the big steps in the decomposition. The sample output accompanying the problem statement suggests some ways to decompose the printing of a month: title and days or title and weeks. These are illustrated in the below Figure 3.1.

printmonth

A month of the printed calendar can be decomposed into headings followed by the dates of the month. PrintMonth can thus be two function calls, one to a function called PrintHeading to print the headings, the other to a function called PrintDates to print the dates. The dates will be printed with a loop. One choice for the loop, corresponding to Figure 3.1a, would print a single date in each iteration; the other, corresponding to Figure 3.1b, would print a whole week in each iteration.

How should PrintHeading be designed?

PrintHeading prints the name of the month, the year, and S M T W T F S. It needs the month and year as parameters, and it must translate the integer value of month into the printed name of the month.

Stop & Predict

What Python construct is most appropriate for printing the month names?

We could code PrintMonth with a sequence of if...then...else statements.

  1. 7
  2. 8

Chapter 3

Durk Jan de Bruin

If..Else statement using the already defined names for the month values results in the following code:

def PrintHeading(month, year):
if month == JAN:
print('January')
if month == FEB:
print('February')
if month == MAR:
print('March')
if month == APR:
print('April')
if month == MAY:
print('May')
if month == JUN:
print('June')
if month == JUL:
print('July')
if month == AUG:
print('August')
if month == SEP:
print('September')
if month == OCT:
print('October')
if month == NOV:
print('November')
if month == DEC:
print('December')
print(year)
print('\n')
print(' S M T W T F S')

Stop & Help

Why are there two spaces before S M T W T F S?

Which decomposition is better?

Figure 3.1 showed two ways to decompose a month. Since both sound promising, we examine them both before making a decision.

Stop & Predict

What are the main parts of each choice for the decomposition PrintDates ?

How should the day-by-day PrintDates be designed?

Figure 3.1a shows a day-by-day decomposition of the printing of dates in a month. The program first prints the blanks at the start of the first week, then prints each date.

Stop & Predict

Write the Python code for printing a single date on the calendar.

The sample output shows that each date is printed in a three-space field. The code for printing the dates will use some sort of loop around the statement print(' ' * 3 + dayForFirst).

How is the blank space before day 1 printed?

The value of dayForFirst tells how many blank dates to print (0 for Sunday, 1 for Monday, and so on). One can use a loop that runs from 1 to dayForFirst to print the blank dates.

Standard Python also allows printing the blank space before the first day all at once in a single write statement:

print(' ' * 3 + dayForFirst)

Stop & Help

What does the statement above print dayForFirst has value 4? For six blank characters to be printed, what value must dayForFirst have?

Stop & Predict

Is this statement always appropriate for printing the blank spaces before the first day of the month?

This statement has a flaw, however; the expression after the string has to have a positive integer value, and an error would occur in months that start on Sunday. If the single write statement is used, the program must either have a special test for Sunday or indent the whole month by one extra space.

What loop prints the days?

The loop to print the days is straightforward

for date = 1 to the number of days in the month do:
print(date + ' ')
if at the end of the week, print a carriage return

This is an instance of the template "do something a specified number of times" introduced in Banners With CLASS, where the "something" is printing a date and possibly a carriage return.

How can the program detect the end of a week?

To figure out how to detect the end of a week (Saturday), we look for a pattern. Consider a sample month, January 2020, from Figure 3.1. The Saturdays are the 4th, the 11th, the 18th, and the 25th. For each week 7 days are added, so the remainder when each of these dates is divided by 7-the "% 7" value-is the same: 4.

The "% 7" values for the dates representing Saturdays will be different in every month. If, however, there were some expression that always had a fixed value on a Saturday, say 0, the test for the end of the week could merely be a comparison of that expression with 0. We notice, for example, that the second week starts when the number of blank dates printed, plus the date, is equal to 7. In Python this becomes dayForFirst + date = 7. The next carriage return should be printed when dayForFirst + date =14, then when dayForFirst + date = 21.

Aha! This pattern reveals that a new line starts when (dayForFirst+date) % 7 == 0. The code looks like this:

  1. 9
  2. 10

Chapter 3

Durk Jan de Bruin

for date = 1 to the number of days in the month:
print(date + ' ')
if (dayForFirst + date) % 7 == 0:
print('\n')

Under what conditions is a carriage return needed after the loop?

With any loop, one should think about the invariant conditions, those that are true at the beginning and the end of each iteration. The key condition to look for here is whether or not a full line of dates has been printed at the end of the loop. The problem statement includes an explicit warning that "exactly one blank line is to be printed between successive months". For some months, the loop will end in the middle of a line, and two print statements will be necessary after the loop; for other months, the loop will end at the beginning of a line, and only one print will be necessary. Thus the following code will need to come after the loop:

print('\n')
if the loop has ended in the middle of a line:
print('\n')

Stop & Predict

Describe as carefully as you can the months for which the loop ends at the beginning of a line.

Stop & Consider

Why will the loop never end at the end of a line?

The way to determine if the loop has ended at the start of a line is merely to repeat the test that caused the print, as follows:

print('\n')
if (dayForFirst + the number of days in the month) % 7 != 0:
print('\n')

What's left

All that's left to compute, and all that the PrintDates function really needs to know other than the month name, is the number of days in the month. The function NumberOfDaysIn provides this value, so we can call it and pass the resulting value to PrintDates to complete the PrintMonth function. The Python Code section contains the result; an outline of the decomposition appears below.

Print the calendar page for the month.
Print the month name and the heading for the weeks.
Print the "body" of the month.
Print blanks to start the first week.

Print the dates.
For each date from 1 to the number of days in the month,
do the following:
Print the date.
If it's a Saturday, print a carriage return.
Print one or two blank lines.

Stop & Predict

How will the week-by-week decomposition differ from the day-by-day decomposition?

How are dates printed week by week?

Figure 3.1b illustrates week-by-week printing of the dates of the month. This decomposition first determines how many weeks are to be printed, then uses a for loop that goes from 1 to the number of weeks to print the weeks. In pseudocode, we have

numWeeks = the number of weeks in the month
for weekNumber in range(0, numWeeks):
print the weekNumber'th week

Stop & Predict

Figure out the pattern for determining the number of weeks in a given month. What types of months create special problems?

How many weeks are there in a month?

Figuring out the number of weeks that need to be printed requires the same sort of searching for a pattern as we did to determine when a new week started in the day-by-day version of PrintDates. The number of weeks depends on the total number of dates in the month plus the number of blank dates at the beginning of the first week. To determine the number of weeks (groups of seven days) the month spans, we want to divide the sum of the blank dates plus the number of days by 7. But what if there is a remainder. The arithmetic is somewhat tricky, since the number of weeks is usually-but not always-one more than the result from dividing by 7. As in Banners With CLASS, a table is helpful.

Stop & Predict

How many entries should the table contain?

dayForFirst + monthlength Number of weeks (dayForFirst + monthlength) // 7
28 4 4
29 5 4
30 5 4
... 5 4
35 5 5
36 6 5
37 6 5

Entries in the table go from the minimum number of days, corresponding to a February that starts on Sunday, to the maximum number, for a 31-day month that starts on Saturday.

We note that the last column in the table is six days ahead of the desired result, and thus produce the following code:

numWeeks = (dayForFirst + monthLength + 6) // 7

Stop & Consider

What is another way to compute the number of weeks using both // and %?

What code prints the week?

Printing a "typical" week (one in the middle of the month) is easy. If we know where to start, we can just loop seven times, from the starting date to 6 plus the starting date, printing the date. After printing a carriage return, we're finished. The code below would handle a typical week.

for date in range(startOfWeek, startOfWeek+6):
print(date + ' ')
print('\n')

  1. 11
  2. 12

Chapter 3

Durk Jan de Bruin

What about the first and last weeks of the month?

The first and last weeks of the month often have leading or trailing blanks. PrintWeek could do one thing for the first week, another for typical weeks, and a third thing for the last week. If the first and last weeks are treated as special cases, almost half the weeks printed will be special.

If the dates are thought of as either blanks or numbers-two different types of data-special code will be necessary to handle each possibility. However, if we view the blanks as special kinds of numbers, then we need merely include a test to check for the "blank numbers" and print blanks for them instead of numeric values. It is easy to determine when blanks appear. Dates past the end of the month are blank. Dates before the beginning of the month are blank. Other dates are printed normally. Using this scheme, January 2020 looks like this:

-2 -1 0 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 32

Stop & Help

Explain why the leading blanks in a month can be thought of as zero or negative dates.

Fixing the code for the typical case to include printing blank dates appropriately involves inserting tests for negative dates and for dates larger than the last day of the month. After filling in the details, the following code for printing a single week results:

def PrintWeek(startOfWeek, monthLength):
date = 0
for date in range(startOfWeek, startOfWeek+6):
if (date > 0) and (date <= monthLength):
print(date + ' ')
else:
print(BLANK + ' ')
print('\n')

How does the program know where to start each week?

All that's left is to compute the starting "date" for the month for the initial call to PrintWeek. To start the first week, we count back from 1 for each blank date. That means the first week starts on date 1 - dayForFirst. Once we know the first date, we can just add 7 to get the start of the next week.

We fill in the details, and arrive at the code in the Python Code section. An outline of the decomposition appears below.

Print the calendar page for the month.
Print the month name and the heading for the weeks.
Print the "body" of the month.
Set numWeeks to the number of weeks in the month.
Determine the starting date of the first week.
Print the weeks.
For weekNumber = 1 to the number of weeks,
do the following:
Print the dates in the weekNumber'th week.
For date = start to start+6, do the following:
If the date is legal, then print it,
otherwise print a blank.
Print a carriage return.
Find the starting date of the next week.

How is each version of PrintMonth tested?

Testing the two versions of PrintMonth may proceed in two ways, black box testing and glass-box testing. The two approaches describe how the test data are chosen.

In black-box testing, the program is regarded as a "black box" whose interior-the program code-is invisible. Black-box test data is derived only from the problem specification; it consists of typical values, well within the program specification, and extreme or boundary values, those near the boundaries of some aspect of the program specification. Sometimes it is also appropriate to test illegal values as well.

Glass-box testing was applied to the NumberOfDaysIn function and in the earlier case studies. In this approach, one chooses test data that sufficiently exercise all statements in the code to be executed.

Neither method is sufficient to prove that the program works correctly in all cases, since testing can only display errors, not prove that no errors exist. Evidence of correctness or incorrectness is what we're looking for, however, and one of the approaches to testing can often provide evidence of errors that would be overlooked if the other approach were used alone.

Stop & Predict

What could go wrong with this program?

What test data does the black- box approach suggest?

We start with the black-box approach and examine the kinds of errors that could arise in printing a calendar:

  • Problems within a month. These include the wrong number of days in the month, the wrong number of days in a week, and a badly formatted heading.
  • Problems from one month to the next. An example is a month that starts on a day other than the one following the day on which the previous month ended. Another example is two months with the wrong number of blank lines between them.
  • Problems with the year. A February with the wrong number of days or a January starting on the wrong day fall into this class.

Test data should expose all these errors.

How much test data are needed?

A complete test would include every possible year. We would need to test 14 years, one with each possible starting day, each tested in a leap year and in a normal year. Obviously, this would take too long to analyze. Can advance planning reduce the task.

Stop & Predict

What cases should be tested?

Let's start with potential problems with a year. If the program correctly prints a year in which January 1 starts on a Wednesday, it will probably work for Tuesday and Thursday as well. If the program is going to fail, it will probably do so at the extreme points in the week, that is, Saturday or Sunday. Similarly, if the program prints February correctly for the year 2018, it will probably do so for 2017 and 2019 as well. Extreme values here are leap years.

  1. 13
  2. 14

Chapter 3

Durk Jan de Bruin

Stop & Help

Generate a set of test years that includes (a) all possibilities for leap years and (b) years in which January 1 falls on a Sunday, a weekday, and a Saturday.

This kind of analysis can be used to narrow down the tests needed to detect errors between printing two consecutive months and errors within a month. An advantage of this program is that it prints all twelve months in the year, so it will be relatively easy to think of a year that tests both typical and extreme conditions.

Stop & Help

What extreme situations are not tested in the year 2020?

What test data does the glass- box approach suggest?

Applying the glass-box approach involves looking at each version of the code. As with the NumberOfDaysIn function and the Banners With CLASS program, we choose values that exercise each part of if statement. Loops introduce a complication. It's easy to pick test values that cause a loop body to be executed once, but that usually doesn't provide much information.

More convincing test data will cause a loop to be executed the minimum number of times, and the maximum number of times.

The day-by-day version of PrintMonth contains two loops, one that prints the blanks at the start of the month, the other that prints the days. The blank-printing loop should be tested with months that have no leading blanks and on months that start on Saturday. The date-printing loop goes from 1 to the month length; thus it should be tested on months that are as short as possible (a February of 28 days) and as long as possible.

Similarly, the loop in the week-by-week version should be tested on months with as few weeks as possible and as many weeks as possible. Some months span six weeks. Some Februarys span only four.

Stop & Help

Select test cases for each version of PrintMonth and test the program.

How should the two versions be compared?

We now have two versions of PrintMonth. How do we decide which is preferable.Programs may be compared in various ways. An obvious way is efficiency which one runs fastest and uses the least memory? However, efficiency is rarely as important as understandability and modifiability. Understandable programs make sense to others. Modifiable programs can be debugged or extended by other programmers.

The day-by-day solution seems understandable in that its decomposition reflects a fairly natural way to think of a calendar month: as a heading followed by a sequence of days. It is also the shorter of the two.

The week-by-week solution seems less understandable. It uses the rather obscure idea of a negative date. Its decomposition of a month into a sequence of weeks is at first glance somewhat unnatural.

But consider modifiability. A real calendar may have Sundays printed in a special typeface. It may have the dates printed in block numerals. The calendar may be printed in the center of the page rather than against the left margin. For months where the 31st falls on a Sunday, it may print something like 24/31. All of these embellishments would be relatively easy to add to the week-by-week solution.

Thus, there are advantages to each approach. The choice depends on the expected uses for the program.

Testing

3.13 Write Python code to test PrintMonth in isolation from the rest of the Calendar Shop program.

Reflection

3.14 Which decomposition do you prefer, the day-by-day decomposition or the week-by-week decomposition? Why?

Reflection

3.15 Suppose every month were to start on a Sunday. How would this change allow the week-by-week version of PrintMonth to be coded more clearly.

Modification,Reflection

3.16 Change each version of PrintMonth to make every month start on Sunday, and decide again which decomposition you prefer.

Modification,Reflection

3.17 Change each version of PrintMonth so that an extra blank space is printed between weekend days and weekdays. For example, the modified PrintMonth should produce the following output for January 2020.

S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Which version of the function is easier to modify?Why?

Debugging

3.18 Insert a bug into either version of the PrintMonth function, let a fellow programmer test it without being able to read the code, and see how long he or she takes to find the bug. What kinds of bugs are easy for a tester to overlook.

Reflection

3.19 Explain how the design of PrintMonth followed the principles of top down design. Describe how the details were postponed to simplify the process.

  1. 15
  2. 16

Chapter 3

Durk Jan de Bruin

Outline of Design and Development Questions

These questions summarize the main points of the commentary.

Understanding the Program

How should one start modifying the program?
What parts of the program tell about the algorithm and the variables?
Should the algorithm or the variables be inspected first?
What is the algorithm for this program?
What is the for statement doing?
What is the purpose of dayForFirst?
How is dayForFirst updated?
What else does ReadYearInfo do?
What is the program decomposition?
Should PrintMonth or NumberOfDaysIn be designed first?

Designing and Developing - NumberOfDaysIn

What does NumberOfDaysIn do?
How should the case for February be coded?
How is LeapDay coded?
When and how should NumberOfDaysIn be tested?
What are good test cases?

Designing and Developing - PrintMonth

How should PrintHeading be designed?
Which decomposition is better?
How should the day-by-day PrintDates be designed?
How is the blank space before day 1 printed?
What loop prints the days?
How can the program detect the end of a week?
Under what conditions is a return needed after the loop?
What's left?
How are dates printed week by week?
How many weeks are there in a month?
What code prints the week?
What about the first and last weeks of the month?
How does the program know where to start each week?
How is each version of PrintMonth tested?
What test data does the black-box approach suggest?
How much test data are needed?
What test data does the glass-box approach suggest?
How should the two versions be compared?


Programmers' Summary

In this case study, code is added to an existing program framework to produce a program to print a calendar for a year input by the user. The first part of the solution involves understanding the program framework; the rest describes the design and development of the missing code.

Python programs have a standard form. Variable declarations and constant and type definitions come at the beginning; subprograms come afterward, with called subprograms preceding the routines that call them. Thus if one wishes to learn about the algorithms used in a program, one reads from back to front. If one wants to know about the data on which the program operates, one reads from front to back. Understanding the algorithm implemented in the program framework was our first task for this problem, so we started with the main program at the end of the code.

The existing code illustrates an interesting use of Python constants to stand for month values. The numeric values 1, 2, ..., 12 could stand for things like clock numbers or grades up through high school, but the names JAN, FEB, and so on say clearly what they mean. In reading the main program, we are able to infer actions of functions it calls without reading the functions; this is a characteristic of well-written code. Writing code that is easy to understand and self-documenting follows the Literacy principle.

Design of one of the missing subprograms, a function to return the number of days in a given month in a given year, involves the use of a if statement. Python provides two conditional constructs, if and else. The form of the if statement highlights the parallelism of similar actions, and thus it seems most appropriate for this application. To increase the information communicated by the if statement, we write the code for the various cases to be as similar as possible.

The other missing subprogram prints the dates in a calendar month. Two decompositions are possible for the code to print the dates in the month. The day-by-day approach involves a loop from 1 to the month length. The week-by-week approach involves a nested loop. The outer loop goes from 1 to the number of weeks; the inner loop prints the days in the selected week. This choice between looping over individuals (dates) and looping over groups (weeks) is a common one in programming problems. Considering two reasonable approaches to solving this problem illustrates the Alternative Paths principle.

The main considerations in the day-by-day approach concern the loop body and its invariant and termination conditions: what's true after each iteration.what's true at the end. Design of the week-by-week loop mainly focuses on the beginning and end of the loop. Analysis of these characteristics of a loop are important in program design and verification.

In general, it is good to avoid special cases in programming. The design of the week-by-week version of PrintMonth avoids special handling of the first and last weeks of the month by introducing the concept of a "negative date," and translating it into a blank when printing.

Both versions use the % operator. In Check That Number! integer arithmetic is used to "take apart" a numeric value. In Banners With CLASS, it is used to compute the size of components of a letter. Here, it is used to represent the cyclic behavior of weeks within a month. We will see other uses in later case studies.

  1. 17
  2. 18

Chapter 3

Durk Jan de Bruin

Given two solutions, how can they be compared.The commentary suggests several ways:

  • conciseness (shorter code is usually easier to understand)
  • naturalness (computer algorithms that are closer to people algorithms are usually easier to understand)
  • modifiability

The problem statement does not mention any potential modifications to be accommodated in the design, so we really don't have grounds for choosing between the two versions on the basis of modifiability. We do mention a few possible modifications that would be easier to make in the week-by-week solution, however.

As in previous case studies, we devise test data that exercise all statements in the code. This solution, however, introduces the approach of black box testing, the inventing of test data solely from the problem specification. The test data is used to check for the following kinds of errors:

  • errors within components (months)
  • coordination errors between components (alignment of the end of one month with the beginning of the next, or spacing between months)
  • errors in the component collection (the year)

Finally, in keeping with the Recycling principle, several variants of templates from earlier case studies are used in this solution:

  • reading and processing until done (a variation on "read one, process one)
  • doing something a given number of times, in cycles
  • doing something a given number of times, accumulating as we go
  • doing something over a given range of values

By reusing templates we are familiar with we save time on design, implementation, and debugging. Recycling templates also helps in program design. Thinking about how to combine templates to solve a problem is often more effective than thinking about how to combine individual elements of the syntax to come up with a solution.


Making Sense of The Calendar Shop

Application

3.20 Think of another of application in which NumberofDaysIn function would be useful.

Reflection

3.21 List the templates introduced in The Calendar Shop. Indicate atleast two other problems each template could help solve.

Reflection

3.22 Critique the design decisions in the Calendar Shop solution. Which would you do differently? Which ones seemed most important.Which ones will you try to remember for the future?

Reflection

3.23 How do you design solutions to complicated problems? How far ahead do you think before you get started?

Reflection

3.24 Suppose that the PrintHeading function used numeric values for the months rather than the constants defined in the original program, as follows:

if month == 1:
print('January')
if month == 2:
print('February')
if month == 3:
print('March')

How would that be inconsistent with the style of the original program? How do the other additions to the program maintain the style of the original program?

Debugging

3.25 After a fellow programmer modifies the day-by-day version of the program, you find that it prints every month with an extra day but with the correct starting day for the month, as shown below:

January 2020

S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

February 2020

S M T W T F S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29

The programmer, when confronted with the error, insists that only one line was changed but can't remember which one. Where could the bug be?

Modification

3.26 Change the Calendar Shop program so that it prints only one month of a given year rather than the whole calendar. Make as few changes to the original program as possible

Modification

3.27 The Gregorian reform to the calendar in 1582 involved the deletion of ten days in October of that year; October 15 was the day after October 4. In order to print October 1582 correctly, which subprograms would have to be modified, and which should not be modified? Explain your answer.

  1. 19
  2. 20

Chapter 3

Durk Jan de Bruin

Application

3.28 Write a program that asks the user for a year and two dates (month+day) in the year, and that prints the two given dates and the number of days between them.


Linking to Previous Case Studies

Reflection

3.29 Compare the process of top down design described in the first three case studies.Identify similarities and differences.

3.30 Compare the cases used to test the programs in the first three case studies. Describe typical cases and extreme cases in a general way.

Application

3.31 Write and test a program that prints a calendar using block digits.

Modification

3.32 Replace the nested loop in the DrawBar function in the Banners With CLASS program by a single loop that calls the print function at appropriate intervals within the loop.

Analysis

3.33 Would an if statement be better or worse than a case statement in the main program of Banners With CLASS? Briefly explain your answer.

Modification

3.34 In the letter-drawing functions in the Banners With CLASS program, integer arithmetic is used to determine the size of the various components. A different approach is to determine an approximate size, then test to see if it's off by one line. For example, one might write the following code for the DrawA function:

legHeight = letterSize // 4
if letterSize % 2 != 0:
legHeight = legHeight + 1

Rewrite the letter-drawing functions to use this approach. Is the result easier to understand? Why or why not?


Incomplete Program Framework

Names of missing functions appear in boldface.

# This program asks the user for a year, and prints a calendar for that year.

JAN = 1
FEB = 2
MAR = 3
APR = 4
MAY = 5
JUN = 6
JUL = 7
AUG = 8
SEP = 9
OCT = 10
NOV = 11
DEC = 12
BLANK = ' '

month = 0 # the index of the month we're printing
year = 0 # the year we're printing the calendar for
dayForFirst = 0 # the index of the first day of month

# We will only deal with years AD.
def Legal(year):
Legal = (year > 0)
return Legal

# Read the year from the user, and compute (using a magic formula) the day on which its January 1 falls. Sunday is 0, Monday 1, etc. The formula to find the first day of the given year was found in the book A Collection of Programming Problems and Techniques, by H.A. Maurer and M.R. Williams (Prentice-Hall, 1972).

def ReadYearInfo(year, dayForFirst):
y = 0
while True:
print('Please type the year for which you want a calendar: ')
year = int(input())
if not Legal(year):
print("Can't make a calendar for that year.")
break
y = year - 1
dayForFirst = (36 + y + (y // 4) - (y // 100) + (y // 400)) % 7

# Main Program

ReadYearInfo(year, dayForFirst)
for month in range(JAN, DEC+1):
PrintMonth(month, year, dayForFirst)
dayForFirst = (dayForFirst + NumberOfDaysIn(month, year)) % 7


NumberOfDaysIn

# Return 0 to represent no leap day, or 1 if it's a leap year.

def LeapDay(year):
if (year % 4 == 0) and (year % 100 != 0):
LeapDay = 1
elif year % 400 == 0:
LeapDay = 1
else:
LeapDay = 0
return LeapDay

  1. 21
  2. 22

Chapter 3

Durk Jan de Bruin

# Return the number of days in the given month in the given year.

def NumberOfDaysIn(month, year):
if (month == JAN or month == MAR or month == MAY or month == JUL or month == AUG or month == OCT or month == DEC):
NumberOfDaysIn = 31
elif month == FEB:
NumberOfDaysIn = 28 + LeapDay(year)
else:
NumberOfDaysIn = 30
return NumberOfDaysIn


Day-by-Day PrintMonth

# Print the month name, year, and the week heading.

def PrintHeading(month, year):
if month == JAN:
print('January')
if month == FEB:
print('February')
if month == MAR:
print('March')
if month == APR:
print('April')
if month == MAY:
print('May')
if month == JUN:
print('June')
if month == JUL:
print('July')
if month == AUG:
print('August')
if month == SEP:
print('September')
if month == OCT:
print('October')
if month == NOV:
print('November')
if month == DEC:
print('December')
print(year)
print('\n')
print(' S M T W T F S')

# Print the dates of the month in nice calendar form.
# First print the blanks in the first week, then print the days of the month, adding a carriage return at the end of each week.

def PrintDates(dayForFirst, monthLength):
date = 0
if dayForFirst > 0:
print(' ' * (dayForFirst * 4),end = '')
for date in range(1, monthLength + 1):
if date == 1 or date == 2 or date == 3 or date == 4 or date == 5 or date == 6 or date == 7 or date == 8 or date == 9:
print(" 0"+str(date), end= ' ')
else:
print(" "+str(date), end= ' ')
if (date + dayForFirst) % 7 ==0:
print('\n')
if (monthLength + dayForFirst) % 7 != 0:
print('\n')
print('\n')

# Print the dates of the given month in the given year, with a nice heading.
# dayForFirst is the day (SUN=0, MON=l, ...) of the first of the month.

def PrintMonth(month, year, dayForFirst):
PrintHeading(month, year)
PrintDates(dayForFirst, NumberOfDaysIn(month, year))


Week-by-Week PrintMonth

# Print a week's worth of dates.
# The date starting the week we're working on is in startOfWeek.
# It may be negative for the first week of a month.

def PrintWeek(startOfWeek, monthLength):
date = 0
for date in range(startOfWeek, startOfWeek + 6):
if (date > 0) and (date <= monthLength):
print(' ' + date)
else:
print(BLANK * 3)
print('\n')

# Print the dates of the month week by week in nice calendar form.
# Do this by first computing how many weeks the month spans.
# Then find the "date" representing Sunday of the first week.
# Then call PrintWeek for each week.

def PrintDates(dayForFirst, monthLength):
numWeeks, startOfWeek, weekNum = 0
numWeeks = (dayForFirst + monthLength + 6) // 7
startOfWeek = 1 - dayForFirst
for weekNum in range(1, numWeeks):
PrintWeek(startOfWeek, monthLength)
startOfWeek = startOfWeek + 7
print('\n')

# Print the dates of the given month in the given year,
# with a nice heading. dayForFirst is the day (SUN=0, MON=l, ...)of the first of the month.

def PrintMonth(month, year, dayForFirst):
PrintHeading(month, year)
PrintDates(dayForFirst, NumberOfDaysIn(month, year))